package cn.yuanyuan.guide.transformer.domain;

import java.util.ArrayList;
import java.util.List;

import cn.yuanyuan.guide.parser.entity.ASTNode;
import cn.yuanyuan.guide.parser.entity.ASTRootNode;
import cn.yuanyuan.guide.parser.entity.Node;

/**
 *
 * @author wuyitao
 * @date 2022/3/27 10:16 PM
 * @updated 2022/03/27
 */
public class Transformer {
    /**
     * So now we have our AST, and we want to be able to visit different nodes with
     * a visitor. We need to be able to call the methods on the visitor whenever we
     * encounter a node with a matching type.
     *
     *   traverse(ast, {
     *     Program: {
     *       enter(node, parent) {
     *         // ...
     *       },
     *       exit(node, parent) {
     *         // ...
     *       },
     *     },
     *
     *     CallExpression: {
     *       enter(node, parent) {
     *         // ...
     *       },
     *       exit(node, parent) {
     *         // ...
     *       },
     *     },
     *
     *     NumberLiteral: {
     *       enter(node, parent) {
     *         // ...
     *       },
     *       exit(node, parent) {
     *         // ...
     *       },
     *     },
     *   });
     *
     * So we define a traverser function which accepts an AST and a
     * visitor. Inside we're going to define two functions...
     */
    public void traverser(Node ast) {
        // Finally we kickstart the traverser by calling `traverseNode` with our ast
        // with no `parent` because the top level of the AST doesn't have a parent.
        traverseNode(ast, null);
    }

    /**
     * A `traverseArray` function that will allow us to iterate over an array and
     * call the next function that we will define: `traverseNode`.
     *
     * @param array 节点列表
     * @param parent 节点父节点
      */
    public void traverseArray(List<Node> array, Node parent) {
        array.forEach(child -> {
            traverseNode(child, parent);
        });
    }

    /**
     * `traverseNode` will accept a `node` and its `parent` node. So that it can
     * pass both to our visitor methods.
     *
     * @param node 当前节点
     * @param parent 父节点
     */
    public void traverseNode(Node node, Node parent) {

        // We start by testing for the existence of a method on the visitor with a
        // matching `type`.
        Visitor methods;
        switch (node.getType()) {
            case "NumberLiteral":
                methods = new NumberLiteralVisitor();
                break;
            case "StringLiteral":
                methods = new StringLiteralVisitor();
                break;
            case "CallExpression":
                methods = new CallExpressionVisitor();
                break;
            default:
                methods = null;
        }


        // If there is an `enter` method for this node type we'll call it with the
        // `node` and its `parent`.
        if (methods != null) {
            methods.enter(node, parent);
        }


        // Next we are going to split things up by the current node type.
        switch (node.getType()) {

            // We'll start with our top level `Program`. Since Program nodes have a
            // property named body that has an array of nodes, we will call
            // `traverseArray` to traverse down into them.
            //
            // (Remember that `traverseArray` will in turn call `traverseNode` so  we
            // are causing the tree to be traversed recursively)
            case "Program":
                ASTRootNode rootNode = (ASTRootNode) node;
                traverseArray(rootNode.getBody(), node);
                break;

            // Next we do the same with `CallExpression` and traverse their `params`.
            case "CallExpression":
                ASTNode astNode = (ASTNode) node;
                traverseArray(astNode.getParams(), node);
                break;

            // In the cases of `NumberLiteral` and `StringLiteral` we don't have any
            // child nodes to visit, so we'll just break.
            case "NumberLiteral":
            case "StringLiteral":
                break;

            // And again, if we haven't recognized the node type then we'll throw an
            // error.
            default:
                throw new RuntimeException(node.getType());
        }

        // If there is an `exit` method for this node type we'll call it with the
        // `node` and its `parent`.
        if (methods != null) {
            methods.exit(node, parent);
        }

    }

    /**
     * Next up, the transformer. Our transformer is going to take the AST that we
     * have built and pass it to our traverser function with a visitor and will
     * create a new ast.
     *
     * ----------------------------------------------------------------------------
     *   Original AST                     |   Transformed AST
     * ----------------------------------------------------------------------------
     *   {                                |   {
     *     type: 'Program',               |     type: 'Program',
     *     body: [{                       |     body: [{
     *       type: 'CallExpression',      |       type: 'ExpressionStatement',
     *       name: 'add',                 |       expression: {
     *       params: [{                   |         type: 'CallExpression',
     *         type: 'NumberLiteral',     |         callee: {
     *         value: '2'                 |           type: 'Identifier',
     *       }, {                         |           name: 'add'
     *         type: 'CallExpression',    |         },
     *         name: 'subtract',          |         arguments: [{
     *         params: [{                 |           type: 'NumberLiteral',
     *           type: 'NumberLiteral',   |           value: '2'
     *           value: '4'               |         }, {
     *         }, {                       |           type: 'CallExpression',
     *           type: 'NumberLiteral',   |           callee: {
     *           value: '2'               |             type: 'Identifier',
     *         }]                         |             name: 'subtract'
     *       }]                           |           },
     *     }]                             |           arguments: [{
     *   }                                |             type: 'NumberLiteral',
     *                                    |             value: '4'
     * ---------------------------------- |           }, {
     *                                    |             type: 'NumberLiteral',
     *                                    |             value: '2'
     *                                    |           }]
     *  (sorry the other one is longer.)  |         }
     *                                    |       }
     *                                    |     }]
     *                                    |   }
     * ----------------------------------------------------------------------------
     *
     *  So we have our transformer function which will accept the lisp ast.
     * @param ast 转化前ast根结点
     */
    public ASTRootNode transformer(ASTRootNode ast) {

        // We'll create a `newAst` which like our previous AST will have a program
        // node.
        ASTRootNode newAst = new ASTRootNode("Program", new ArrayList<>());

        // Next I'm going to cheat a little and create a bit of a hack. We're going to
        // use a property named `context` on our parent nodes that we're going to push
        // nodes to their parent's `context`. Normally you would have a better
        // abstraction than this, but for our purposes this keeps things simple.
        //
        // Just take note that the context is a reference *from* the old ast *to* the
        // new ast.
        ast.setContext(newAst.getBody());

        // We'll start by calling the traverser function with our ast and a visitor.
        traverser(ast);

        // At the end of our transformer function we'll return the new ast that we
        // just created.
        return newAst;
    }
}
